feat(connector): GrabPay connector integration - #2063
feat(connector): GrabPay connector integration#2063peeyushshukla-juspay wants to merge 87 commits into
Conversation
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
…refund and refund_sync
|
request_code field (line 202) and its request_code: None init (line 1295) are unused, please drop them. |
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
| fn session_token_from_connector_feature_data( | ||
| connector_feature_data: Option<&common_utils::pii::SecretSerdeValue>, | ||
| ) -> Option<String> { | ||
| let metadata = | ||
| utils::to_connector_meta_from_secret::<serde_json::Value>(connector_feature_data.cloned()) | ||
| .ok()?; | ||
|
|
||
| metadata | ||
| .get("session_token") | ||
| .or_else(|| metadata.get("access_token")) | ||
| .and_then(serde_json::Value::as_str) | ||
| .map(ToOwned::to_owned) | ||
| } |
There was a problem hiding this comment.
please raise an issue to revert this once Euler side changes for session token is done
There was a problem hiding this comment.
already created a ticket 👍
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
| }); | ||
|
|
||
| Ok(Self { | ||
| partner_group_tx_id: router_data.request.connector_transaction_id, |
There was a problem hiding this comment.
partnerGroupTxID here is connector_transaction_id — Grab's txID — but the charge sent partnerGroupTxID = partnerTxID, the merchant reference (line 1018). GrabPay groups a refund with its charge by partnerGroupTxID, so these won't link. It also bypasses validate_partner_tx_id, which every other partner-namespace id goes through. Should this come from connector_feature_data.partner_tx_id?
There was a problem hiding this comment.
partnerGroupTxID is correctly using the original charge partner namespace id, while originTxID uses Grab's charge txID. This matches GrabPay's refund contract and links the refund to the original charge correctly.
| serde_json::json!(response.description), | ||
| ); | ||
| metadata.insert("reason".to_string(), serde_json::json!(response.reason)); | ||
| if let Some(session_token) = session_token { |
There was a problem hiding this comment.
The access token stored here gets replayed as the PSync/Refund/RSync bearer by session_token_from_connector_feature_data (grabpay.rs:339). expires_in from the token response is dropped, so a sync or refund after expiry sends a dead token and 401s instead of refreshing. Carry the expiry alongside it and skip the fallback once it's past.
There was a problem hiding this comment.
This is session_token flow, it is for transaction and does not have expiry. This should ideally be taken from session_token field. Since Euler does not have support for UCS session token flow, i have added this fallback to connector feature data. This will be reverted post Euler UCS bridge support is added. Also this token has an expiry of 1year, hence dropping the expires_in.
Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions.
aa26ff8
shuklatushar226
left a comment
There was a problem hiding this comment.
check base url once
| bypass_urls = ["localhost", "local"] | ||
|
|
||
| [connectors] | ||
| grabpay.base_url = "https://partner-api.stg-myteksi.com/grabpay/partner/v2" |
There was a problem hiding this comment.
once confirm dev sandbox and production url
| "MerchantAuthenticationService/CreateServerAuthenticationToken", | ||
| "MerchantAuthenticationService/CreateServerSessionAuthenticationToken", | ||
| "PaymentService/CreateOrder" |
There was a problem hiding this comment.
CreateServerAuthenticationToken and CreateOrder aren't implemented — grabpay.rs registers both under not_implemented, and create_all_prerequisites! declares only Authorize, PSync, Refund, RSync, ServerSessionAuthenticationToken, Authenticate. test_ucs.rs picks suites straight off this list, so both will run and fail. check_connector_specs won't catch it: it flags missing entries, not extra ones.
| response: Ok(PaymentsResponseData::TransactionResponse { | ||
| resource_id, | ||
| redirection_data: None, | ||
| connector_metadata: Some(serde_json::json!({ |
There was a problem hiding this comment.
PSync rebuilds connector_metadata from scratch while Authorize merges via build_complete_connector_feature_data. A caller chaining Authorize → PSync → Refund and forwarding the latest connector_feature_data loses session_token (needed by Refund/RSync get_headers) and currency (needed by currency_from_connector_feature_data when refund_money is absent). Can this reuse the merge helper?
| .ok_or_else(|| error_stack::report!(errors::WebhookError::WebhookBodyDecodingFailed))?; | ||
| status | ||
| .trim() | ||
| .parse::<T>() |
There was a problem hiding this comment.
strum's FromStr has no default variant, so any status GrabPay adds later fails here and the webhook is rejected outright. #[serde(other)] Unknown only catches the literal "unknown", which leaves the Unknown => IncomingWebhookEventUnspecified and Unknown => Pending arms unreachable in practice. The HTTP response path degrades gracefully through serde; this one doesn't.
| resource_id: None, | ||
| status: None, | ||
| connector_response_reference_id: None, | ||
| error_code: error, |
There was a problem hiding this comment.
On ?error=access_denied this sets error_code but leaves status: None, and process_composite_verify_redirect_response never checks it. should_do_session_token then returns false (no code), so Authorize runs without a token and dies in get_headers with MissingRequiredField { field_name: "session_token" } — the RPC errors out before the caller ever sees access_denied.
Description
Adds the GrabPay one-time-charge redirect wallet connector with Create Order, OAuth authorization, redirect verification, payment sync, refund, and refund sync support.
Flows Implemented
types.CompositePaymentService/Authorizetypes.CompositePaymentService/VerifyRedirectResponsetypes.CompositePaymentService/Gettypes.CompositePaymentService/Refundtypes.CompositeRefundService/Gettypes.EventService/HandleEventAdditional Changes
GRAB_PAYpayment method type, andgrabpayRedirectwallet data.Testing
1. Authorize - Redirect Pending
Response:
{ "authorizeResponse": { "connectorTransactionId": "<REDACTED_TRANSACTION_ID>", "status": "AUTHENTICATION_PENDING", "statusCode": 302, "redirectionData": { "uri": { "uri": "https://partner-api.grab.com/grabid/v1/oauth2/authorize?<REDACTED>" } }, "connectorFeatureData": { "value": "{\"state\":\"<REDACTED>\",\"nonce\":\"<REDACTED>\",\"code_verifier\":\"<REDACTED>\",\"redirect_uri\":\"https://example.com/payment/response\",\"partner_tx_id\":\"<REDACTED_TRANSACTION_ID>\",\"currency\":\"PHP\",\"request_code\":\"<REDACTED>\"}" } }, "createOrderResponse": { "connectorOrderId": "<REDACTED_REQUEST_CODE>", "status": "PENDING", "statusCode": 200 }, "compositeStatus": "COMPLETED" }2. Verify Redirect Response - Charge Success
Response:
{ "verifyRedirectResponse": { "rawConnectorResponse": { "value": "{\"code\":\"<REDACTED>\",\"state\":\"<REDACTED>\",\"error\":null}" }, "connectorFeatureData": { "value": "<REDACTED_CONNECTOR_FEATURE_DATA>" } }, "accessTokenResponse": { "accessToken": { "value": "<REDACTED_ACCESS_TOKEN>" }, "tokenType": "Bearer", "expiresInSeconds": "31535999", "status": "OPERATION_STATUS_SUCCESS", "statusCode": 200 }, "authorizeResponse": { "connectorTransactionId": "<REDACTED_CONNECTOR_TRANSACTION_ID>", "status": "CHARGED", "statusCode": 200, "connectorFeatureData": { "value": "{\"txID\":\"<REDACTED_CONNECTOR_TRANSACTION_ID>\",\"status\":\"success\",\"paymentMethod\":\"GPWALLET\",\"description\":\"\",\"reason\":\"\"}" } } }3. Payment Sync - Success
Response:
{ "getResponse": { "connectorTransactionId": "<REDACTED_CONNECTOR_TRANSACTION_ID>", "status": "CHARGED", "statusCode": 200, "amount": { "minorAmount": "1", "currency": "PHP" }, "connectorFeatureData": { "value": "{\"txID\":\"<REDACTED_CONNECTOR_TRANSACTION_ID>\",\"status\":\"success\",\"paymentMethod\":\"GPWALLET\",\"description\":\"\",\"txStatus\":\"success\",\"reason\":\"\"}" } } }4. Refund - Success
Response:
{ "refundResponse": { "connectorRefundId": "<REDACTED_REFUND_ID>", "status": "REFUND_SUCCESS", "statusCode": 200, "connectorTransactionId": "<REDACTED_TRANSACTION_ID>", "rawConnectorResponse": { "value": "{\"txID\":\"<REDACTED_CONNECTOR_REFUND_TRANSACTION_ID>\",\"status\":\"success\",\"paymentMethod\":\"GPWALLET\",\"description\":\"\",\"txStatus\":\"success\",\"reason\":\"\",\"echo\":null}" } } }5. Refund Sync - Success
Response:
{ "refundResponse": { "merchantRefundId": "<REDACTED_REFUND_ID>", "connectorRefundId": "<REDACTED_REFUND_ID>", "status": "REFUND_SUCCESS", "statusCode": 200, "connectorTransactionId": "<REDACTED_TRANSACTION_ID>", "rawConnectorResponse": { "value": "{\"txID\":\"<REDACTED_CONNECTOR_REFUND_TRANSACTION_ID>\",\"status\":\"success\",\"paymentMethod\":\"GPWALLET\",\"description\":\"\",\"txStatus\":\"success\",\"reason\":\"\",\"echo\":null}" } } }Validation
cargo fmt --checkcargo test -p connector-integration -- grabpaycargo check -p connector-integration -p composite-service -p grpc-server